﻿Imports IICModel
Imports org.model.lib.db
Imports org.codegen.common.ProgressIndicator
Imports System.Text

Namespace PeriodProcessing

    

    Public Interface IPeriodProcessor
        Property diplomasEarned() As List(Of DiplomaEarned)
        ''' <summary>
        ''' List of certificates that were earned
        ''' </summary>
        Property certificatesEarned() As List(Of CertificationEarned)
        ''' <summary>
        ''' Dictionary that contains only one record per student.  The studentId is the key of the Dictionary
        ''' </summary>
        ReadOnly Property period() As Period

        Property ExamsStudentProcessor() As ICreditsAndExamsProcessor
        ''' <summary>
        ''' Creates diplomas earned for a period
        ''' </summary>
        ''' <remarks></remarks>
        Sub processPeriodAndPersist()
        ''' <summary>
        ''' Saves all Earned Certificates and Diplomas to the database
        ''' </summary>
        ''' <remarks></remarks>
        Sub persistToDatabase()

        Function membersProcessedCount() As Integer

        Function certificatesEarnedCount() As Integer

        Function diplomasEarnedCount() As Integer

        ReadOnly Property PeriodProcess() As PeriodProcess

    End Interface

    Public Class PeriodProcessor
        Implements IProgressable
        Implements IPeriodProcessor

        Public Shared TRACK_STUDENT_NUMBER As Integer = 4191

        Private _periodProcess As PeriodProcess = Nothing

        Private _period As Period
        Private _ExamsStudentProcessor As ICreditsAndExamsProcessor

        Public Property PeriodProcessRunDate As DateTime? = Now

        Public Property diplomasEarned As List(Of DiplomaEarned) Implements IPeriodProcessor.diplomasEarned

        '''<summary>
        '''here we store the certificationSatisfiedRequirements that were not used for earning a certificate
        '''For example, if a certificate has 2 certificate requirements and in this period we are processing
        '''only one was passed, we save this one to the database (its certificationearned will be null)
        '''and will be picked up on subsequent period processes
        '''</summary>
        Public Property unusedCertSatisfiedRequirements As List(Of CertificationSatisfiedRequirement) = New List(Of CertificationSatisfiedRequirement)

        '''<summary>
        '''here we store the DiplomaSatisfiedRequirements that were not used for earning a diploma.  
        '''For example, if a diploma has 2 certificate requirements and in this period we are processing
        '''only one was passed, we save this one to the database (its diplomaearnedid will be null)
        '''these have to be stored in the database and will be picked up on subsequent period processes
        '''</summary>
        Public Property unusedDiplomaSatisfiedRequirements As List(Of DiplomaSatisfiedRequirement) = New List(Of DiplomaSatisfiedRequirement)

        ''' <summary>
        ''' List of certificates that were earned
        ''' </summary>
        Public Property certificatesEarned As List(Of CertificationEarned) Implements IPeriodProcessor.certificatesEarned

        ''' <summary>
        ''' Dictionary that contains only one record per student.  The studentId is the key of the Dictionary
        ''' </summary>
        Public Property dictStudentSatisfiedDiplomaReqs As Dictionary(Of String, IStudentSatisfiedDiplomaReqs)

        ''' <summary>
        ''' Class that has the responsibility to save records to the database.
        ''' </summary>
        Public Property Persistor As DBProcessorPersistor = New DBProcessorPersistor()

        Public ReadOnly Property period As Period Implements IPeriodProcessor.period
            Get
                Return _period
            End Get
        End Property

        ''' <summary>
        ''' we need to reload the period from the database, in case 
        ''' the period object on the tree is not aware of changes in the 
        ''' child objects of the period
        ''' </summary>
        ''' <param name="Period">Period we are going to process</param>
        ''' <remarks></remarks>
        Public Sub New(ByVal Period As Period)
            'reload from database! do not just take the period object, 
            'which may have unsaved changes

            Me._period = PeriodDataUtils.findByKey(Period.PeriodId)

        End Sub

        Public Property ExamsStudentProcessor As ICreditsAndExamsProcessor Implements IPeriodProcessor.ExamsStudentProcessor
            Get
                If _ExamsStudentProcessor Is Nothing Then
                    _ExamsStudentProcessor = New CreditsAndExamsProcessor(Me.period)
                End If
                Return _ExamsStudentProcessor
            End Get
            Set(ByVal value As ICreditsAndExamsProcessor)
                _ExamsStudentProcessor = value
            End Set
        End Property

        ''' <summary>
        ''' Processes the period but does **not** save to the database
        ''' </summary>
        ''' <remarks></remarks>
        Public Sub processPeriod()

            RaiseEvent processStart(Me, New ProgressableEventArgs(4))

            RaiseEvent processGoing(Me, New ProgressableEventArgs(1, 4, "Processing Exams..."))
            Me.processCreditsAndClasses()

            RaiseEvent processGoing(Me, New ProgressableEventArgs(2, 4, "Processing Credits..."))
            Me.determineEarnedCertificates()

            RaiseEvent processGoing(Me, New ProgressableEventArgs(3, 4, "Determining Earned Diplomas..."))
            Me.determineEarnedDiplomas()

        End Sub

        ''' <summary>
        ''' Processes a period for diplomas and certifications earned, and saves to the database
        ''' </summary>
        ''' <remarks></remarks>
        Public Sub processPeriodAndStoreToDb() Implements IPeriodProcessor.processPeriodAndPersist

            Trace.TraceInformation("------------ START " & Me.period.PeriodDescription)
            Me.processPeriod()

            RaiseEvent processGoing(Me, New ProgressableEventArgs(4, 4, "Saving to database..."))
            Me.persistToDatabase()

            RaiseEvent processFinished(Me, New ProgressableEventArgs())
            Trace.TraceInformation("------------ DONE " & Me.period.PeriodDescription)

        End Sub

        Private Function getStudentSatisfiedDiplomaReqs( _
                                ByVal std As Student, _
                                ByVal diplomaType As DiplomaType) As IStudentSatisfiedDiplomaReqs

            Dim ret As IStudentSatisfiedDiplomaReqs
            Dim skey As String = std.StudentId & "," & diplomaType.DiplomaTypeId
            ' Debug.WriteLine("retrieve StudentSatisfiedDiplomaReqs:" & skey)
            If (Me.dictStudentSatisfiedDiplomaReqs Is Nothing) Then
                Me.dictStudentSatisfiedDiplomaReqs = New Dictionary(Of String, IStudentSatisfiedDiplomaReqs)
            End If

            If Me.dictStudentSatisfiedDiplomaReqs.ContainsKey(skey) Then
                ret = dictStudentSatisfiedDiplomaReqs.Item(skey)
            Else
                ret = New StudentSatisfiedDiplomaReqs(std, diplomaType)
                Me.dictStudentSatisfiedDiplomaReqs.Add(skey, ret)
            End If

            Return ret

        End Function



        ''' <summary>
        ''' Checks grades and exceptions for satisfied requirements
        ''' </summary>
        ''' <remarks></remarks>
        Private Sub processCreditsAndClasses()

            Me.ExamsStudentProcessor.processCredits()
            Me.ExamsStudentProcessor.processExams()

            'at this point, I have all student-certificationType pairs in memory

        End Sub

        Private Sub determineEarnedCertificates()

            'logger.Debug("----------------BEGIN determineEarnedCertificates")

            Me.certificatesEarned = New List(Of CertificationEarned)

            If Me.ExamsStudentProcessor.dictStudentSatisfiedCertReqs Is Nothing Then Exit Sub

            ' loop thru each StudentSatisfiedCertReqs in dictStudentSatisfiedReqs and see if the
            ' student earned a certificate
            For Each v As IStudentSatisfiedCertReqs In Me.ExamsStudentProcessor.dictStudentSatisfiedCertReqs.Values
                If v.certificationType.DiplomaSuperTypeId = Me.period.DiplomaSuperTypeId Then
                    'If v.Student Is Nothing Then
                    '    Stop
                    'End If
                    Dim sd As New StringBuilder(String.Format("Checking {0} for {1}, diploma Super Type:{2}", _
                                            v.Student.MemberNumber, v.certificationType.CertificationType, v.certificationType.DiplomaSuperTypeId))

                    If v.isCertificationEarned Then
                        sd.Append(":CertificationType Passed")
                        Dim ce As CertificationEarned = CertificationEarnedFactory.CreateForStudentAndCertificate( _
                                     v.certificationType.CertificationTypeId, _
                                      v.Student.StudentId)

                        'add all certification requirements satisified to this certification earned
                        v.SatisfiedRequirements.ForEach(Sub(x)
                                                            ce.AddCertificationSatisfiedRequirement(x)
                                                        End Sub)

                        'add to collection of certificates earned
                        Me.certificatesEarned.Add(ce)

                        '---------------------------
                        For Each dq As DiplomaRequirement In v.certificationType.DiplomaRequirements
                            'Debug.WriteLine("adding diploma req """ & dq.Diploma.DiplomaType & _
                            '                """, CertificationTypeId: " & _
                            '                ce.CertificationTypeId & " to student """ & v.Student.StudentId & """")
                            Dim ssr As IStudentSatisfiedDiplomaReqs = Me.getStudentSatisfiedDiplomaReqs(v.Student, dq.Diploma)
                            Dim dsr As DiplomaSatisfiedRequirement = DiplomaSatisfiedRequirementFactory.Create
                            dsr.DiplomaRequirementId = dq.DiplomaRequirementId
                            dsr.certificationEarned = ce

                            ssr.addSatifiedDiplomaRequirement(dsr)
                        Next
                        '-----------------------------
                    Else
                        'add these to the satisified requirements that may be used in future period processing
                        'to earn a certificate
                        sd.Append(":CertificationType **NOT*** Passed")
                        Me.unusedCertSatisfiedRequirements.AddRange(v.SatisfiedRequirements)
                    End If
                    If v.Student.MemberNumber = PeriodProcessor.TRACK_STUDENT_NUMBER Then
                        Trace.TraceInformation(sd.ToString)
                    End If

                End If
            Next


        End Sub

        Private Sub determineEarnedDiplomas()

            Me.diplomasEarned = New List(Of DiplomaEarned)
            If Me.dictStudentSatisfiedDiplomaReqs Is Nothing Then Exit Sub

            For Each x As IStudentSatisfiedDiplomaReqs In Me.dictStudentSatisfiedDiplomaReqs.Values
                If x.isDiplomaEarned Then

                    Dim ce As DiplomaEarned = DiplomaEarnedFactory.CreateForStudentAndCertificate( _
                        x.diplomaType.DiplomaTypeId, _
                        x.Student.StudentId)

                    ce.SerialNo = Me.SerialNoCreator.getSerialNumber(x.diplomaType, x.Student)
                    'add all diploma satisfied requirements satisified to this diploma earned
                    x.SatisfiedRequirements.ForEach(Sub(de)
                                                        ce.AddDiplomaSatisfiedRequirement(de)
                                                    End Sub)
                    Me.diplomasEarned.Add(ce)
                Else
                    Me.unusedDiplomaSatisfiedRequirements.AddRange(x.SatisfiedRequirements)
                End If

            Next

        End Sub

        ''' <summary>
        ''' Saves all Earned Certificates and Diplomas to the database
        ''' </summary>
        ''' <remarks></remarks>
        Public Sub persistToDatabase() Implements IPeriodProcessor.persistToDatabase

            Me.Persistor.persistToDatabase(Me)

        End Sub

        Function membersProcessedCount() As Integer Implements IPeriodProcessor.membersProcessedCount

            Return Me.ExamsStudentProcessor.classMembersProcessed.Count

        End Function

        Function certificatesEarnedCount() As Integer Implements IPeriodProcessor.certificatesEarnedCount
            Return Me.certificatesEarned.Count
        End Function

        Function diplomasEarnedCount() As Integer Implements IPeriodProcessor.diplomasEarnedCount
            Return Me.diplomasEarned.Count
        End Function

        ReadOnly Property PeriodProcess() As PeriodProcess Implements IPeriodProcessor.PeriodProcess
            Get
                If _periodProcess Is Nothing Then
                    _periodProcess = PeriodProcessFactory.Create
                    _periodProcess.PeriodProcessDate = Me.PeriodProcessRunDate
                    _periodProcess.PeriodId = Me.period.PeriodId
                End If

                Return _periodProcess
            End Get

        End Property

#Region "IProgressable "


        Public Event processFinished(ByVal sender As Object, ByVal e As ProgressableEventArgs) _
                    Implements IProgressable.processFinished

        Public Event processGoing(ByVal sender As Object, ByVal e As ProgressableEventArgs) _
                    Implements IProgressable.processGoing

        Public Event processStart(ByVal sender As Object, ByVal e As ProgressableEventArgs) _
                    Implements IProgressable.processStart

        Public Property wasCancelled As Boolean Implements IProgressable.wasCancelled


#End Region

        Private _serialNoCreator As ISerialNoCreator

        Private Function SerialNoCreator() As ISerialNoCreator

            If _serialNoCreator Is Nothing Then

                If Me.period.DiplomaSuperTypeId = EnumDiplomaSuperTypes.PAS OrElse _
                    Me.period.DiplomaSuperTypeId = EnumDiplomaSuperTypes.DAS Then

                    _serialNoCreator = New PasSerialNoCreator
                Else
                    _serialNoCreator = New DefaultSerialNoCreator
                End If

            End If

            Return _serialNoCreator

        End Function

    End Class


End Namespace
